package checker

import (
	"kumachan/standalone/rpc/kmd"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/typsys"
)

func makeSchemaTable(types ([] TypeDef), mic ModuleInfoCollection) (kmd.SchemaTable, KmdTypeInfoMapping, source.Errors) {
	var mapping, err = makeKmdTypeInfoMapping(types, mic)
	if err != nil { return nil, nil, err }
	var table = make(kmd.SchemaTable)
	{ var err = (func() source.Errors {
	var errs source.Errors
	for _, def := range types {
		var info, has_info = mapping[def.TypeDef]
		if !(has_info) { panic("something went wrong") }
		var id = info.Id
		var err = (func() *source.Error {
		var schema, err = (func() (kmd.Schema, *source.Error) {
			switch content := def.Content.(type) {
			case *typsys.Box:
				var loc = def.Location
				{ var record, is_record = getRecord(content.InnerType)
				if is_record {
					var fields_kmd = make(map[string] kmd.RecordField)
					for i, field := range record.Fields {
						var field_t = field.Type
						var field_kmd, err = kmdGetType(field_t, mapping, loc)
						if err != nil { return nil, err }
						fields_kmd[field.Name] = kmd.RecordField {
							Type:  field_kmd,
							Index: uint(i),
						}
					}
					return kmd.RecordSchema { Fields: fields_kmd }, nil
				} }
				{ var tuple, is_tuple = getTuple(content.InnerType)
				if is_tuple {
					var elements_kmd = make([] *kmd.Type, len(tuple.Elements))
					for i, el := range tuple.Elements {
						var el_kmd, err = kmdGetType(el, mapping, loc)
						if err != nil { return nil, err }
						elements_kmd[i] = el_kmd
					}
					return kmd.TupleSchema { Elements: elements_kmd }, nil
				} }
				var inner = content.InnerType
				var el_kmd, err = kmdGetType(inner, mapping, loc)
				if err != nil { return nil, err }
				return kmd.TupleSchema { Elements: [] *kmd.Type { el_kmd } }, nil
			case *typsys.Enum:
				var indexes = make(map[kmd.TypeId] uint)
				for i, case_def := range content.CaseTypes {
					var case_info, exists = mapping[case_def]
					if !(exists) {
						return nil, source.MakeError(def.Location,
							E_KmdEnumCaseNotSerializable {
								Case: case_def.Name.ItemName,
							})
					}
					var case_id = case_info.Id
					indexes[case_id] = uint(i)
				}
				return kmd.EnumSchema { CaseIndexMap: indexes }, nil
			default:
				panic("something went wrong")
			}
		})()
		if err != nil { return err }
		table[id] = schema
		return nil
		})()
		source.ErrorsJoin(&errs, err)
	}
	return errs
	})()
	if err != nil { return nil, nil, err }
	return table, mapping, nil }
}

func makeKmdTypeInfoMapping(types ([] TypeDef), mic ModuleInfoCollection) (KmdTypeInfoMapping, source.Errors) {
	var mapping = make(KmdTypeInfoMapping)
	var occurred = make(map[kmd.TypeId] struct{})
	var err = (func() source.Errors {
		var errs source.Errors
		for _, def := range types {
			var mi = mic.GetTypeModuleInfo(def)
			var manifest = &(mi.ModManifest)
			var meta = &(def.Metadata.Data)
			var id = kmd.MakeTypeId (
				manifest.Vendor,
				manifest.Project,
				meta.Name,
				meta.Version,
			)
			var err = (func() (*source.Error) {
				// 1. checked duplicate
				var _, duplicate = occurred[id]
				if duplicate {
					return source.MakeError(def.Location,
						E_KmdDuplicateTypeId {
							Id: id.String(),
						})
				}
				occurred[id] = struct{}{}
				// 2. check parameters
				if len(def.Parameters) > 0 {
					return source.MakeError(def.Location,
						E_KmdGenericTypeDeclaredSerializable {})
				}
				// 3. check content
				var kmd_type, ok = (func() (*kmd.Type, bool) {
					switch content := def.Content.(type) {
					case *typsys.Box:
						var _, is_record = getRecord(content.InnerType)
						if is_record {
							return kmd.AlgebraicType(kmd.Record, id), true
						} else {
							return kmd.AlgebraicType(kmd.Tuple, id), true
						}
					case *typsys.Enum:
						return kmd.AlgebraicType(kmd.Enum, id), true
					default:
						return nil, false
					}
				})()
				if !(ok) {
					return source.MakeError(def.Location,
						E_KmdTypeDefContentNotSerializable {
							TypeDefName: def.Name.String(),
						})
				}
				mapping[def.TypeDef] = KmdTypeInfo {
					Id:   id,
					Type: kmd_type,
				}
				return nil
			})()
			source.ErrorsJoin(&errs, err)
		}
		return errs
	})()
	if err != nil { return nil, err }
	return mapping, nil
}


